#include <all_wlib.h>
#pragma hdrstop
//#pragma package(smart_init)
#pragma resource "*.dfm"
#pragma link "HWindow"
#pragma link "ozf_Base"
#pragma link "HRectButton"
#pragma link "HMenus"
#pragma link "HLog"
#pragma link "BkList"
#pragma link "HPaintScroll"
#pragma link "HSpacer"
#pragma link "TB97Ctls"

#include "ozf_Track.h"
#pragma link "HGrid"
#pragma link "HDesignPanel"

#define INV( v ) op.v = !op.v;

HCoordPath Path;
HCoord     CurrentCoord;
double     LastHead;
MyRect     Field;

DEF_SWITCH( Track )

CONSTSTR WPSectionNames[] = {
   _LNG("Place number"),
   _LNG("Length from current position"),
   _LNG("Latitude (N)"),
   _LNG("Longitude (E)"),
   "X",
   "Y"
};

CONSTSTR SectionNames[] = {
   _LNG("Waypoint number"),
   _LNG("Track distance from prev point"),
   _LNG("Track distance from very begining"),
   _LNG("Direct distance from prev point"),
   _LNG("Speed at ptack part"),
   _LNG("Point start Lon"),
   _LNG("Point start Lat"),
   _LNG("Direct distance from track start"),
   _LNG("Full track speed"),
   _LNG("Point track-points number"),
   _LNG("Point start time"),
   _LNG("Point walk time"),
   _LNG("Full track walk time"),
   _LNG("Point start X"),
   _LNG("Point start Y") };

CONSTSTR StatMaxText[] = {
   /*mX,mY*/    "00000000", "00000000",
   /*Lat,Lon*/  "0000.000", "0000.000",
   /*X,Y*/      "00000000", "00000000",
   /*offX,offY*/"00000",    "00000",
   /*W,H*/      "000000",   "000000"
};

//---------------------------------------------------------------------------
// TTrackForm
//---------------------------------------------------------------------------
__fastcall TTrackForm::TTrackForm(TComponent* Owner)
    : TOziBaseForm(Owner)
  {  int n;

     //Props
     Cfg2TrackOptions( op );

     image->DoubleBuffered     = true;
     wndPoints->Color          = clNone;
     FCurrentLine              = -1;
     FOffsetX                  = 0;
     FOffsetY                  = 0;
     LastHead                  = 0;
     LastSave                  = time(NULL);
     Overrides.OnControlChange = idOverrideChange;

   //CFG
     Init( "Track", (TForm**)&Form );

     AutoMove             = true;
     LogTrack             = true;

     HCaptionButton_t ws;

   //Set options
     RefreshOptions();

   //Controls window
     ws = op.CState;
     if ( ws != hcbMinimize )
       wndControls->Height = op.CHeight;
      else
       wndControls->WindowState = ws;

   //Points window
     ws = op.PState;
     if ( ws != hcbMinimize )
       wndPoints->Height = op.PHeight;
      else
       wndPoints->WindowState = ws;

   //Places window
     ws = op.PlState;
     if ( ws != hcbMinimize )
       wndPlaces->Width = op.PlHeight;
      else
       wndPlaces->WindowState = ws;

   //Headers
     for( n = 0; n < pList->Cols->Count; n++ )
       pList->ColWidths[n] = GOPT( Message("PSec%d",n), pList->ColWidths[n] );

     for( n = 0; n < wpList->Cols->Count; n++ )
       wpList->ColWidths[n] = GOPT( Message("PlSec%d",n), wpList->ColWidths[n] );

   //Load last used trach
     if ( op.LoadAutosave ) {
       CONSTSTR nm = op.AutosaveFile.c_str();
       if ( nm && nm[0] ) {
         Load( nm, false );
         if ( Path.Count() )
           CurrentCoord = *Path.LastObject()->LastPoint();
       }
     }
}

void TTrackForm::Save( CONSTSTR nm )
  {  HAutoPtr<HOStream> os;

     if ( op.CompressData )
       os = new LZHFileOStream(  MakeStartLocalPath(nm).Text() );
      else
       os = new HFileOStream(  MakeStartLocalPath(nm).Text() );

     if ( !os.Ptr() || !os->Assigned() ) {
       AskMessage( Handle, NULL, LNG("Error open output file.\nFile: %s\nError: %s"), nm, FIO_ERROR );
       return;
     }

     if ( !Path.Save( *os.Ptr() ) )
       AskMessage( Handle, NULL, LNG("Error writing output file.\nFile: %s\nError: %s"), nm, FIO_ERROR );

     Refresh();
}

void TTrackForm::Load( CONSTSTR nm, bool StopLogging )
  {  HAutoPtr<HIStream> s( OpenStreamFile( MakeStartLocalPath(nm).Text() ) );
     if ( !s.Ptr() ) {
       AskMessage( Handle, NULL, LNG("Error open source file.\nFile: %s\nError: %s"), nm, FIO_ERROR );
       return;
     }

     if ( !Path.Load( *s.Ptr() ) )
       AskMessage( Handle, NULL, LNG("Error loading file.\nFile: %s\nError: %s"), nm, FIO_ERROR );

     if ( StopLogging && Path.Count() ) {
       LogTrack = false;
       AutoMove = false;
       CurrentCoord = *Path.LastObject()->LastPoint();
     }

     wpList->ItemsCount = Path.Places.Count();
     Refresh();
}

//---------------------------------------------------------------------------
void __fastcall TTrackForm::FormClose(TObject *Sender, TCloseAction &Action)
  {  int n;

  //Autosave
     if ( op.Autosave ) {
       CONSTSTR nm = op.AutosaveFile.c_str();
       if ( nm && nm[0] )
         Save( nm );
     }

   //Controls
     op.CState   = wndControls->WindowState;
     op.CHeight  = wndControls->Height;
     op.PState   = wndPoints->WindowState;
     op.PHeight  = wndPoints->Height;
     op.PlState  = wndPlaces->WindowState;
     op.PlHeight = wndPlaces->Width;

   //2Cfg
     Track2CfgOptions( op );

     for( n = 0; n < pList->Cols->Count; n++ )
       SOPT( Message("PSec%d",n), pList->ColWidths[n] );

     for( n = 0; n < wpList->Cols->Count; n++ )
       SOPT( Message("PlSec%d",n), wpList->ColWidths[n] );

  //Base
     TOziBaseForm::FormClose( Sender, Action );
}

void __fastcall TTrackForm::idOverrideChange( TObject *Sender )
  {  TCanvas *dc = Canvas;
     int      n, w;
     MyRect   r;

     if ( Sender == status ) {
       dc->Font = status->Font;

       for( n = 0; n < status->Panels->Count; n++ ) {
         TStatusPanel *sp = status->Panels->Items[n];

         r.SetNull();
         if ( (n&1) == 0 ) {
           TC_TextOut( dc, &r, sp->Text, DT_SINGLELINE );
           sp->Width = r.Width()+4;
         } else {
           TC_TextOut( dc, &r, StatMaxText[n/2], DT_SINGLELINE );
           sp->Width = r.Width()+4;
         }
       }
     } else
      ;
}

void TTrackForm::RefreshOptions( void )
  {
     Scale = op.Scale;

#define CORR( v ) v = v+1; v = v-1;
     wndControls->BorderBottom->Size = op.ResizeSize; CORR( wndControls->Height )
     wndPoints->BorderTop->Size      = op.ResizeSize; CORR( wndPoints->Height )
     wndPlaces->BorderTop->Size      = op.ResizeSize; CORR( wndPlaces->Height )
#undef CORR

     Refresh();
}

//---------------------------------------------------------------------------
DECL_PROP_SET( TTrackForm, int, Scale )
  {
     val = Max( 5, val );
     if ( FScale == val ) return;
     FScale = val;

     val = cbScale->Items->IndexOf( AnsiString(FScale) );
     if ( val != -1 )
       cbScale->ItemIndex = val;

     op.Scale = FScale;
     Refresh();
}

DECL_PROP_GET( TTrackForm, PHCoord,     CurPlace ) { return Path.Places.Item( wpList->ItemIndex ); }
DECL_PROP_GET( TTrackForm, PHCoordLine, ListItem ) { return Path.Item( Path.Count() - pList->ItemIndex - 1 ); }

DECL_PROP_SET( TTrackForm, bool, AutoMove )
  {
     op.AutoMove = val;
     btnAutoMove->Down = val;
     Refresh();
}

DECL_PROP_SET( TTrackForm, bool, LogTrack )
  {
     op.LogTrack = val;
     btnLogTrack->Down = val;
}

DECL_PROP_SET( TTrackForm, int, CurrentLine ) { if ( FCurrentLine == val ) return; FCurrentLine = val; Refresh(); }
DECL_PROP_SET( TTrackForm, int, OffsetX )     { if ( FOffsetX == val ) return; FOffsetX = val; Refresh(); }
DECL_PROP_SET( TTrackForm, int, OffsetY )     { if ( FOffsetY == val ) return; FOffsetY = val; Refresh(); }

int TTrackForm::DrawY( int y )  { return image->Height - (y - Field.y1) * image->Height / (-Field.Height()); }
int TTrackForm::DrawX( int x )  { return (x - Field.x) * image->Width / Field.Width(); }

int TTrackForm::FieldY( int y )  { return CurrentCoord.y - (y - image->Height/2) * (-Field.Height()) / image->Height + OffsetY; }
int TTrackForm::FieldX( int x )  { return CurrentCoord.x + (x-image->Width/2) * Field.Width() / image->Width + OffsetX; }

void TTrackForm::SetSizes( void )
  {
/*
  int cn = Path.Count(),
         n, w;

     for( w = 0, n = 0; n < header->Sections->Count; n++ ) {
       THeaderSection *s = header->Sections->Items[n];
       w += s->Width + 4;
     }
     header->Width = Max( header->Width, w );

     pList->Width      = header->Width;
     pList->Height     = cn * pList->ItemHeight + 2;
     pList->ItemsCount = cn;
*/
}

void TTrackForm::Refresh( void )
  {
     idPos( CurrentCoord.Lat, CurrentCoord.Lon, 0, LastHead, 0, 0, CurrentCoord.x, CurrentCoord.y );
     wndPoints->BorderTop->Caption = Message( "Waypoints: %d", Path.Count() );

     //SetSizes();
     pList->ItemsCount = Path.Count();
}

void TTrackForm::DrawCoordLine( TCanvas *dc,int lNum, bool isCurrent, bool isLast )
  {  PHCoordLine l = Path[ lNum ];
     int         x, y, n;
     PHCoord     p, p1;
     TColor      cl, cl1;
     int         pptSize = op.PathPointWidth;
     MyRect      line;

   //Path
     dc->Pen->Width = op.PathLineWidth;
     cl             = isCurrent ? op.PathSelLineColor : op.PathLineColor;

     p = &l->Start;
     line.x = DrawX(p->x);
     line.y = DrawY(p->y);

     for( n = 0; n < l->Count(); n++, p = p1 ) {
       p1 = l->Item(n);

       if ( !p->Size( *p1 ) )
         continue;

       line.x1 = DrawX(p1->x);
       line.y1 = DrawY(p1->y);
       if ( line.x == line.x1 && line.y == line.y1 )
         continue;

       MyRect nLine( line.Normalized() );
       if ( nLine.x1 < 0 || nLine.y1 < 0 ||
            nLine.x > image->Width || nLine.y > image->Height )
         continue;

       if ( op.DrawPathPoints )
         TC_Ellipse( dc, MyRect(line.x1-pptSize/2, line.y1-pptSize/2, line.x1+pptSize/2, line.y1+pptSize/2),
                     cl );

       TC_Line( dc, line, cl );

       if ( ShineLine == lNum && n == ShinePoint )
         TC_Ellipse( dc,
                     MyRect(line.x1-pptSize/2-2, line.y1-pptSize/2-2,
                     line.x1+pptSize/2+2, line.y1+pptSize/2+2),
                     op.NearestPointColor );

       line.x = line.x1;
       line.y = line.y1;
     }
     dc->Pen->Width = 1;

   //Direct line
     if ( isLast && l->Count() ) {
       cl             = op.PathDirectColor;
       dc->Pen->Width = op.PathDirectWidth;
       p = &l->Start;
       p1 = &CurrentCoord;
       TC_Line( dc, DrawX(p->x), DrawY(p->y), DrawX(p1->x), DrawY(p1->y), cl );
       dc->Pen->Width = 1;
     }

   //Start
     x  = DrawX(l->Start.x);
     y  = DrawY(l->Start.y);
     cl = (isCurrent && !isLast) ? op.PathCurrentColor : op.PathPointColor;
     TC_FillEllipse( dc, MyRect( x-pptSize/2, y-pptSize/2, x+pptSize/2, y+pptSize/2 ), cl );

     if ( isCurrent && !isLast && l->Count() ) {
       cl             = op.PathSelDirectColor;
       dc->Pen->Width = op.PathDirectWidth;
       p = &l->Start;
       p1 = l->LastObject();
       TC_Line( dc, DrawX(p->x), DrawY(p->y), DrawX(p1->x), DrawY(p1->y), cl );
       dc->Pen->Width = 1;
     }
}

void TTrackForm::DrawLineInfo( TCanvas *dc, PHCoordLine l )
  {  MyString s;
     int      x, y, n;

     dc->Brush->Color = op.InfoColor;
     if ( op.DrawStartTime ) {
       x  = DrawX(l->Start.x);
       y  = DrawY(l->Start.y);
       s  = TimeStr( l->Start.Measure );
       SetBkMode( dc->Handle, OPAQUE );
       TextOut( dc->Handle, x+15, y-10, s.c_str(), s.Length() );
     }

     PHCoord p = l->LastObject();
     if ( !p ) return;

     s  = "";
     x  = DrawX(p->x);
     y  = DrawY(p->y);

     if ( op.DrawNumber )
       s.cat( " %d \n", Path.IndexOf(l)+1 );

     if ( op.DrawDist )
       s.cat( "%d/%d\n", l->Size(), l->DirectSize() );

     if ( op.DrawEtaTime ) {
       n = p->Measure - l->Start.Measure;
       s.cat( "%02d:%02d:%02d\n", n/3600, (n%3600)/60, n%60 );
     }

     if ( op.DrawAvgSpeed &&
          p->Measure != l->Start.Measure ) {
       double d = ((double)p->Measure - l->Start.Measure) / 3600;
       s.cat( "%3.3lf\n", ((double)l->Size())/1000/d, ((double)l->DirectSize())/1000/d );
     }

     if ( s.Length() ) {
       MyRect r( x, y+10, 0, 0 );
       DrawText( dc->Handle, s.c_str(), s.Length()-1, r, DT_CALCRECT|DT_LEFT|DT_TOP );
       r.Offset( -r.Width()/2, 0 );
       TC_Fill( dc, r, clDefault );
       SetBkMode( dc->Handle, TRANSPARENT );
       DrawText( dc->Handle, s.c_str(), s.Length()-1, r, DT_LEFT|DT_TOP );
     }
}

//---------------------------------------------------------------------------
void __fastcall TTrackForm::imagePaint(TObject *Sender, TCanvas *dc)
  {
     if ( !Field.Height() || !Field.Width() ) {
       Refresh();
       return;
     }

     MyRect   r( 0,0,image->Width,image->Height );
     int      x,y,x1,y1,
              n;
     TColor   cl;

   //Fill
     TC_Fill( dc, r, op.BkColor );

   //Grid
     cl = op.GridColor;
     for( y1 = Field.y1 - Field.y1%Scale; y1 < Field.y; y1 += Scale )
       TC_Line( dc, r.x, DrawY(y1), r.x1, DrawY(y1), cl );

     for( x = Field.x - Field.x%Scale; x < Field.x1; x += Scale )
       TC_Line( dc, DrawX(x), r.y, DrawX(x), r.y1, cl );

   //Paths
     for( n = 0; n < Path.Count(); n++ )
       DrawCoordLine( dc, n, n == CurrentLine, n == Path.Count()-1 );

     if ( op.DrawInfo ) {
       int ofsz = dc->Font->Size;
       dc->Font->Size  = op.InfoFont;
       dc->Font->Color = op.InfoFontColor;
       for( n = 0; n < Path.Count(); n++ )
         DrawLineInfo( dc, Path[n] );
       dc->Font->Size = ofsz;
     }

   //Places points
     x1 = op.PlaceSize;
     y1 = op.PlaceCenterSize;

     for( n = 0; n < Path.Places.Count(); n++ ) {
       PHCoord p = Path.Places[n];

       if ( p->x < Field.x || p->x > Field.x1 ||
            p->y < Field.y1 || p->y > Field.y )
         continue;

       x  = DrawX(p->x);
       y  = DrawY(p->y);

       cl = op.PlaceColor;
       TC_Ellipse( dc, MyRect( x-x1/2, y-x1/2, x+x1/2, y+x1/2 ), cl );

       cl = op.PlaceHandleColor;
       TC_Line( dc, x-y1/2, y, x+y1/2, y, cl );
       TC_Line( dc, x, y-y1/2, x, y+y1/2, cl );

       if ( op.DrawPlaceNum ) {
         dc->Brush->Color = op.PlaceNumColor;
         dc->Font->Color  = op.PlaceNumFontColor;
         dc->Font->Size   = op.PlaceNumFontSize;

         SetBkMode( dc->Handle, OPAQUE );
         CONSTSTR m = Message( "%d", n+1 );
         TextOut( dc->Handle, x+y1/2, y-y1/2, m, strlen(m) );
       }
     }

   //Current
     x = DrawX(CurrentCoord.x);
     y = DrawY(CurrentCoord.y);
     //Head
     x1 = op.CurLength;
     MyPoint p1( x,y ),
             p2( x + x1*cos((LastHead-90)*M_PI/180),y + x1*sin((LastHead-90)*M_PI/180) );
     dc->Pen->Color   = op.CurLineColor;
     dc->Brush->Color = dc->Pen->Color;

     dc->Pen->Width = op.CurLineWidth;
     TC_Line( dc, p1.x, p1.y, p2.x, p2.y, clDefault );

     dc->Pen->Width = 1;
     TC_LineShape( dc, p1, p2, HFigureFillArrow, op.CurArrowSize, 30 );
     //Point
     x1 = op.CurPointWidth;
     TC_FillEllipse( dc, MyRect( x-x1/2, y-x1/2, x+x1/2, y+x1/2 ), op.CurPointColor );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::idOnButton(TObject *Sender,const PHCustomWindowBorder Border, const HCaptionButton_t Button)
{
     if ( Button == hcbHelp )
       aCfgExecute(NULL);
     TOziBaseForm::idOnButton( Sender, Border, Button );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::idPos( double Lat,double Lon,double vSpeed,double Head,double Mag,double Alt, int x, int y )
  {
   //Values
     LastHead = Head;
     if ( op.AutoMove )
       CurrentCoord.Set( Lat, Lon, x, y );

     double fx = GDIToMMX( image->Canvas->Handle, image->Width ) / 10 * Scale,
            fy = GDIToMMX( image->Canvas->Handle, image->Height ) / 10 * Scale;
     Field.Set( CurrentCoord.x + OffsetX - fx/2, CurrentCoord.y + OffsetY + fy/2,
                CurrentCoord.x + OffsetX + fx/2, CurrentCoord.y + OffsetY - fy/2 );

   //Add new point
     if ( RealValues &&
          op.LogTrack ) {
       PHCoordLine line = Path.LastObject();
       if ( line ) {
         line->AddPoint( Lat, Lon, x, y, op.Avg );
         pList->Invalidate();
       }
     }

   //Update info
     status->Panels->Items[ 5]->Text = Message( "%.3f", Lat );
     status->Panels->Items[ 7]->Text = Message( "%.3f", Lon );
     status->Panels->Items[ 9]->Text = Message( "%d", x  );
     status->Panels->Items[11]->Text = Message( "%d", y  );
     status->Panels->Items[13]->Text = Message( "%d", OffsetX  );
     status->Panels->Items[15]->Text = Message( "%d", OffsetY  );
     status->Panels->Items[17]->Text = Message( "%.3f", ((double)Field.Width())/1000 );
     status->Panels->Items[19]->Text = Message( "%.3f", ((double)-Field.Height())/1000 );

   //Repaint
     image->Invalidate();
     wpList->Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::idScaleChange(TObject *Sender)      { Scale = atoi( cbScale->Text.c_str() ); }
void __fastcall TTrackForm::clientResize(TObject *Sender)       { Refresh(); }
//---------------------------------------------------------------------------
void __fastcall TTrackForm::imagePopupMenuItemCheck( TObject *Sender, TMenuItem *mi )
  {
     switch( mi->Tag ) {
       case 100: mi->Checked = op.DrawInfo;        break;
       case 101: mi->Checked = op.DrawStartTime;   break;
       case 102: mi->Checked = op.DrawEtaTime;     break;
       case 103: mi->Checked = op.DrawDist;        break;
       case 104: mi->Checked = op.DrawAvgSpeed;    break;
       case 105: mi->Checked = op.DrawPathPoints;  break;
       case 106: mi->Checked = op.DrawNumber;      break;
     }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::S1Click(TObject *Sender) { INV( DrawStartTime ); Refresh(); }
void __fastcall TTrackForm::i2Click(TObject *Sender) { INV( DrawEtaTime ); Refresh(); }
void __fastcall TTrackForm::D2Click(TObject *Sender) { INV( DrawDist ); Refresh(); }
void __fastcall TTrackForm::A1Click(TObject *Sender) { INV( DrawAvgSpeed ); Refresh(); }
void __fastcall TTrackForm::D3Click(TObject *Sender) { INV( DrawPathPoints ); Refresh(); }
void __fastcall TTrackForm::D4Click(TObject *Sender) { INV( DrawNumber ); Refresh(); }
void __fastcall TTrackForm::aDrawInfoExecute(TObject *Sender) { INV( DrawInfo ); Refresh(); }

void __fastcall TTrackForm::idAutoMove(TObject *Sender) { AutoMove = !op.AutoMove; }
void __fastcall TTrackForm::idLogTrack(TObject *Sender) { LogTrack = !op.LogTrack; }

void __fastcall TTrackForm::idMoveLeft(TObject *Sender)  { OffsetX = OffsetX - Scale; }
void __fastcall TTrackForm::idMoveUp(TObject *Sender)    { OffsetY = OffsetY + Scale; }
void __fastcall TTrackForm::idMoveRight(TObject *Sender) { OffsetX = OffsetX + Scale; }
void __fastcall TTrackForm::idMoveDown(TObject *Sender)  { OffsetY = OffsetY - Scale; }
void __fastcall TTrackForm::idMoveReset(TObject *Sender) { OffsetX = 0; OffsetY = 0; }
//---------------------------------------------------------------------------
int TTrackForm::LocateLinePoint( int x, int y, int& l )
  {  HCoord c( 0, 0, FieldX(x), FieldY(y) );
     int    rc = -1,
            dest = 1000000;

     l = -1;
     for( int n = 0; n < Path.Count(); n++ ) {
       PHCoordLine pl = Path[n];
       for( int i = 0; i < pl->Count(); i++ ) {
         PHCoord p = pl->Item(i);
         int     sz;
         if ( (sz=c.QSize(*p)) > 0 && sz < dest ) {
           l    = n;
           rc   = i;
           dest = sz;
         }
       }
     }

 return rc;
}
void __fastcall TTrackForm::mnuInsertPointClick(TObject *Sender)
  {
     if ( ShinePoint <= 0 || ShineLine < 0 )
       return;

     if ( !AskYesNo( Handle, NULL,
                     LNG("Insert track point into %d-th waypoint line at %d-th position?"),
                     ShineLine+1, ShinePoint+1 ) )
       return;

     PHCoordLine l = Path[ ShineLine ],
                 nl = Path.AddAt( ShineLine+1, new HCoordLine );
     int         n;

     nl->Start = *l->Item( ShinePoint );
     for( n = ShinePoint+1; n < l->Count(); n++ )
       nl->Add( *l->Item( n ) );

     for( n = l->Count()-ShinePoint; n >= 0; n-- )
       l->DeleteNum( ShinePoint+1 );

     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aCfgExecute(TObject *Sender)
{
  if ( TrackConfigure(this,op) ) {
    Scale = op.Scale;
    RefreshOptions();
  }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aTrackEditExecute(TObject *Sender)
  {  PHCoordLine l = ListItem;
     if ( !l ) return;

     AskMessage( Handle, LNG("Not realized yet!") );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aTrackAddExecute(TObject *Sender)
  {  HCoord coord;

     LogTrack = true;

     PHCoordLine p = Path.LastObject();
     if ( p ) {
       if ( !p->Start.Diff( CurrentCoord.x, CurrentCoord.y, op.Avg ) )
         return;

       coord = *p->LastPoint();
     } else
       coord = CurrentCoord;

     p = Path.Add( new HCoordLine );
       p->Start = coord;
       p->Start.Measure = time(NULL);

     if ( Path.Count() == 1 )
       LastSave = time( NULL );

     Refresh();
     pList->Row = 1;
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aZoomIncExecute(TObject *Sender)
  {
    __try{ cbScale->ItemIndex = cbScale->ItemIndex+1; }catch(...){ return; }
    idScaleChange(NULL);
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aZoomDecExecute(TObject *Sender)
{
    __try{ cbScale->ItemIndex = cbScale->ItemIndex-1; }catch(...){}
    idScaleChange(NULL);
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::imageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
  {
     CursorPos.x = X;
     CursorPos.y = Y;

     if ( Shift.Contains( ssDouble ) )
       mnuInsertPointClick( NULL );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::imageMouseMove( TObject *Sender, TShiftState Shift, int X, int Y )
  {  int fX = FieldX(X),
         fY = FieldY(Y);

     CursorPos.x = X;
     CursorPos.y = Y;

     status->Panels->Items[1]->Text = Message( "%d", fX );
     status->Panels->Items[3]->Text = Message( "%d", fY );

     int lnum, pnum;
     pnum = LocateLinePoint( X, Y, lnum );

     if ( lnum != ShineLine || pnum != ShinePoint ) {
       ShinePoint = pnum;
       ShineLine  = lnum;
       image->Invalidate();
     }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::overridePopupPopup(TObject *Sender)
  {
     Overrides.PrepareOverridePopup( overridePopup->Items );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aDelCurPointExecute(TObject *Sender)
  {  PHCoordLine l  = Path.Item( CurrentLine ),
                 l1;

     if ( !l ||
          !AskYesNo( Handle, NULL,
                     LNG("Delete waypoint number %d?"), CurrentLine+1 ) )
       return;

     if ( Path.Count() == 1 &&
          !AskYesNo( Handle, NULL, LNG("This is single waypoint in a path.\nStop track recording?") ) )
       return;

     if ( CurrentLine > 0 && (l1 = Path.Item( CurrentLine-1 )) != NULL ) {
       if ( AskYesNo( Handle, NULL, LNG("Waypoint contains track points.\nAdd points to previous waypoint?") ) ) {
         l1->Add( l->Start );
         for( int n = 0; n < l->Count(); n++ )
           l1->Add( *l->Item(n) );
       }
     } else
     if ( CurrentLine == 0 && (l1 = Path.Item( CurrentLine+1 )) != NULL ) {
       if ( AskYesNo( Handle, NULL, LNG("Waypoint contains track points.\nAdd points to next waypoint?") ) ) {
         l1->AddAt( 0, l1->Start );
         l1->Start = l->Start;
         for( int n = 0; n < l->Count(); n++ )
           l1->AddAt( n, *l->Item(n) );
       }
     }

     Path.Delete( l );
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::pPopupMenuItemCheck(TObject *Sender, TMenuItem *mi )
  {
      switch( mi->Tag ) {
        case 100:
        case 104:
        case 102: mi->Enabled = CurrentLine >= 0;
              break;

        case 101: mi->Caption = CurrentLine >= 0 ? Message( "Point: %d", CurrentLine+1 ) : "- No point -";
              break;
      }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::SaveTimerTimer(TObject *Sender)
  {
      if ( !Path.Count() ) return;

      SaveTimer->Enabled = false;
      __try{
        if ( !op.Autosave )
          return;

        time_t tm = time(NULL);
        if ( tm-LastSave < op.AutosavePer*60 )
          return;
        LastSave = tm;

        CONSTSTR nm = op.AutosaveFile.c_str();
        if ( !nm || !nm[0] )
          return;

        Save( nm );

      }__finally{
        SaveTimer->Enabled = true;
      }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::idLoad(TObject *Sender)
  {
     if ( Path.Count() &&
          !AskYesNo( Handle, NULL, LNG("Discard current track and load new data?") ) )
       return;

     Data->open->Filter     = LNG("Path files|*.path|All files|*.*");
     Data->open->Title      = LNG("Select file to load path...");
     Data->open->DefaultExt = "path";
     Data->open->InitialDir = GOPT( "DataPath", MakeStartLocalPath("").Text() );
     if ( !Data->open->Execute() ) return;
     SOPT( "DataPath", FPath( Data->open->FileName.c_str() ) );

     HOperation rop(this);
     rop.SetText( LNG("Loading track file:\n%s..."),Data->open->FileName.c_str() );
     Load( Data->open->FileName.c_str() );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::idSave(TObject *Sender)
  {
     if ( !Path.Count() )
       return;

     Data->save->Filter     = LNG("Path files|*.path|All files|*.*");
     Data->save->Title      = LNG("Select file to save path...");
     Data->save->DefaultExt = "path";
     Data->save->InitialDir = GOPT( "DataPath", MakeStartLocalPath("").Text() );
     if ( !Data->save->Execute() ) return;
     SOPT( "DataPath", FPath(Data->save->FileName.c_str()) );

     HOperation rop(this);
     rop.SetText( LNG("Save current track to file:\n%s..."),Data->save->FileName.c_str() );
     Save( Data->save->FileName.c_str() );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::FormKeyUp(TObject *Sender, WORD &Key,TShiftState Shift)
  {
      TOziBaseForm::FormKeyUp( Sender, Key,Shift );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::statusDrawPanel( TStatusBar *status, TStatusPanel *p, const TRect &Rect )
  {
      TC_TextOut( status->Canvas, Rect, p->Text.c_str(), clNone,
                  Align2DTFlags(p->Alignment,true) | DT_VCENTER | DT_SINGLELINE );
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::imageResize(TObject *Sender)
  {
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::wpPopupMenuItemCheck( TObject *Sender, TMenuItem *mi )
  {  PHCoord p = CurPlace;

     switch( mi->Tag ) {
       case 100:
       case 104:
       case 102: mi->Enabled = p != NULL;
              break;

       case 101: mi->Caption = wpList->ItemIndex >= 0 ? Message( "Point: %d", wpList->ItemIndex+1 ) : "- No point -";
              break;
     }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift)
{
      if ( Shift == (TShiftState()<<ssCtrl) && Key == 'L' ) { idLoad( NULL );     Key = 0; } else
      if ( Shift == (TShiftState()<<ssCtrl) && Key == 'S' ) { idSave( NULL );     Key = 0; } else
      if ( Shift == (TShiftState()<<ssCtrl) && Key == 'M' ) { idAutoMove( NULL ); Key = 0; } else
      if ( Shift == (TShiftState()<<ssCtrl) && Key == 'T' ) { idLogTrack( NULL ); Key = 0; } else
      if ( Shift == TShiftState() ) {
        switch( Key ) {
          case  VK_LEFT: { idMoveRight( NULL ); Key = 0; } break;
          case VK_RIGHT: { idMoveLeft( NULL );  Key = 0; } break;
          case    VK_UP: { idMoveDown( NULL );  Key = 0; } break;
          case  VK_DOWN: { idMoveUp( NULL );    Key = 0; } break;
          case VK_CLEAR: { idMoveReset( NULL ); Key = 0; } break;
        }
      }
        ;
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aTrackClearExecute(TObject *Sender)
{
     if ( !Path.Count() ||
          !AskYesNo( Handle, NULL,
                     LNG("Delete all waypoints and stop track recording?") ) )
       return;

     Path.DeleteAll();
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aWPAddExecute(TObject *Sender)
  {  HCoord it = CurrentCoord;

     if ( !EditPlacePos( &it ) )
       return;

     Path.Places.Add( it );
     wpList->ItemsCount = Path.Places.Count();
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aWPDelExecute(TObject *Sender)
  {  int     num = wpList->ItemIndex;
     PHCoord p = Path.Places.Item( num );
     if ( !p ||
          !AskYesNo( Handle, NULL, LNG("Delete %d-th place from list?"), num+1 ) )
       return;

     Path.Places.DeleteNum( num );
     wpList->ItemsCount = Path.Places.Count();
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aWPEditExecute(TObject *Sender)
  {  PHCoord p = CurPlace;
     if ( !p ||
          !EditPlacePos( p ) )
       return;

     wpList->RepaintRow( wpList->ItemIndex );
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aWPClearExecute(TObject *Sender)
  {        if ( !Path.Places.Count() ||
           !AskYesNo( Handle, NULL, LNG("Delete all places from list?") ) )
        return;

      Path.Places.DeleteAll();
      wpList->ItemsCount = Path.Places.Count();
      Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aTrackGoExecute(TObject *Sender)
  {  PHCoordLine l = ListItem;
     if ( !l ) return;

     OffsetX      = 0;
     OffsetY      = 0;
     AutoMove     = false;
     CurrentCoord = *l->LastPoint();
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::pListHOnGetCellText(TObject *Sender, int nX,int nY, AnsiString &Value)
  {  int         Index = Path.Count() - nY - 1;
     PHCoordLine p = Path.Item( Index );
     if ( !p ) return;
     PHCoordLine pl;
     int         n, cn;
     time_t      tm;
//!!

     switch( nX ) {
   //Number
      case 0: Value = Message( "%d", Index+1 );
          break;

   //By-Track dist from prev point
      case 1: Value = FormatKm(  p->Size() );
          break;

   //By-track dist from very begining
      case 2: for( cn = 0, n = Index; n >= 0; n-- )
                cn += Path[n]->Size();
              Value = FormatKm( cn );
          break;

   //Direct dist from prev point
      case 3: Value = FormatKm( p->DirectSize() );
          break;

   //Point by-track speed
      case 4: cn = p->Size();
              tm = p->LastPoint()->Measure - p->Start.Measure;

              if ( !tm )
                Value = "";
               else
                Value = Message( "%.3f", ((float)cn) * 3.6 / tm );
          break;

   //Point start Lat
      case 5: Value = Message( "%.3f", p->Start.Lat );
          break;

   //Point start Lon
      case 6: Value = Message( "%.3f", p->Start.Lon );
          break;

   //Direct dist from very begining
      case 7: pl = Path.Item(0);
              if ( pl != NULL )
                Value = FormatKm(  p->LastPoint()->Size( pl->Start ) );
          break;

   //Full track speed
      case 8: for( cn = 0, n = Index; n >= 0; n-- )
                cn += Path[n]->Size();

              for( tm = 0, n = Index; n >= 0; n-- ) {
                pl = Path[n];
                tm += pl->LastPoint()->Measure - pl->Start.Measure;
              }

              if ( !tm )
                Value = "";
               else
                Value = Message( "%.3f", ((float)cn) * 3.6 / tm );
          break;

   //Point track-points number
      case 9: Value = Message( "%d", p->Count() );
          break;

   //Point start time
      case 10: Value = TimeStr( p->Start.Measure );
          break;

   //Point walk time
      case 11: Value = TimeDiffStr( p->LastPoint()->Measure - p->Start.Measure );
          break;

   //Full track walk time
      case 12: for( tm = 0, n = Index; n >= 0; n-- ) {
                pl = Path[n];
                tm += pl->LastPoint()->Measure - pl->Start.Measure;
              }
              Value = TimeDiffStr( tm );
          break;

   //Point start X
      case 13: Value = Message( "%d", p->Start.x );
          break;

   //Point start Y
      case 14: Value = Message( "%d", p->Start.y );
          break;
     }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::pListHOnHeaderMove( TObject *Sender,PHCustomGridCell Cell,
                                                TShiftState Shift, int mX, int mY, int nX,int nY)
  {
     hlpStatus->SimpleText = LNG(SectionNames[nX]);
}
void __fastcall TTrackForm::pListClick(TObject *Sender) { CurrentLine = Path.Count() - pList->ItemIndex-1; }
//---------------------------------------------------------------------------
void __fastcall TTrackForm::pListHOnHeaderClick(TObject *Sender,
      PHCustomGridCell Cell, TMouseButton Button, TShiftState Shift,
      int mX, int mY, int nX, int nY)
{
      if ( Button == mbRight )
        Overrides.PrepareOverridePopup( mnuOverride, pList );
}
void __fastcall TTrackForm::pListHOnCellClick(TObject *Sender,
      PHCustomGridCell Cell, TMouseButton Button, TShiftState Shift,
      int mX, int mY, int nX, int nY)
{
//!!
      if ( Button == mbRight ) {
        SendMessage( pList->Handle, WM_LBUTTONDOWN, ShiftToWMParam(Shift), MK_DWORD(mY,mX) );
        pListClick( NULL );
        Overrides.PrepareOverridePopup( mnuOverride, pList );
      } else
      if ( Button == mbLeft && Shift.Contains(ssDouble) ) {
        aTrackGoExecute( NULL );
      } else
      if ( Button == mbLeft ) {
        pListClick( NULL );
      } else
        ;
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::wpListMouseDown( TObject *Sender,TMouseButton Button, TShiftState Shift, int X, int Y )
  {
//??
      if ( Button == mbRight ) {
        SendMessage( wpList->Handle, WM_LBUTTONDOWN, ShiftToWMParam(Shift), MK_DWORD(Y,X) );
        Overrides.PrepareOverridePopup( mnuWPOverride, wpList );
      }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::aWPGoExecute(TObject *Sender)
  {  PHCoord p = CurPlace;
     if ( !p ) return;
//??
     AutoMove = false;
     CurrentCoord = *p;
     Refresh();
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::wpListHOnGetCellText(TObject *Sender, int nX,int nY, AnsiString &Value)
  {  PHCoord p = Path.Places.Item( nY );
     if ( !p ) return;
//++
     switch( nX ) {
     //Number
       case 0: Value = Message( "%d", nY+1 ); break;
     //Sz
       case 1: Value = FormatKm( CurrentCoord.Size( *p ) ); break;
     //N
       case 2: Value = Message( "%.5f", p->Lat ); break;
     //E
       case 3: Value = Message( "%.5f", p->Lon ); break;
     //X
       case 4: Value = Message( "%d", p->x ); break;
     //Y
       case 5: Value = Message( "%d", p->y ); break;
     }
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::wpListHOnHeaderClick(TObject *Sender,
      PHCustomGridCell Cell, TMouseButton Button, TShiftState Shift,
      int mX, int mY, int nX, int nY)
{
      if ( Button == mbRight )
        Overrides.PrepareOverridePopup( mnuWPOverride, wpList );
}
void __fastcall TTrackForm::wpListHOnCellClick(TObject *Sender,
      PHCustomGridCell Cell, TMouseButton Button, TShiftState Shift,
      int mX, int mY, int nX, int nY)
{
      if ( Button == mbRight ) {
        SendMessage( wpList->Handle, WM_LBUTTONDOWN, ShiftToWMParam(Shift), MK_DWORD(mY,mX) );
        Overrides.PrepareOverridePopup( mnuWPOverride, wpList );
      } else
      if ( Button == mbLeft && Shift.Contains(ssDouble) ) {
        aWPGoExecute( NULL );
      } else
        ;
}
//---------------------------------------------------------------------------
void __fastcall TTrackForm::wpListHOnHeaderMove( TObject *Sender,PHCustomGridCell Cell,
                                                 TShiftState Shift, int mX, int mY, int nX, int nY)
{
  hlpStatus->SimpleText = LNG(WPSectionNames[nX]);
}
//---------------------------------------------------------------------------

